package com.yeskery.boot.log.record.starter.proxy;

import com.yeskery.boot.log.record.starter.annotation.LogRecord;
import com.yeskery.boot.log.record.starter.core.*;
import com.yeskery.boot.log.record.starter.parser.IFunctionService;
import com.yeskery.boot.log.record.starter.extend.IOperatorGetService;
import com.yeskery.boot.log.record.starter.parser.LogRecordEvaluationContext;
import com.yeskery.boot.log.record.starter.parser.LogRecordValueParser;
import com.yeskery.boot.log.record.starter.extend.ILogRecordService;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.aop.support.AopUtils;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import java.lang.reflect.Method;
import java.util.*;
import java.util.stream.Collectors;

/**
 * 日志记录增强逻辑处理拦截器
 * @author dushuangjiang
 * @date 2021-11-16 16:04
 */
public class LogRecordInterceptor implements MethodInterceptor, BeanFactoryAware {

    private static final Log log = LogFactory.getLog(LogRecordInterceptor.class);

    private final LogRecordOperationSource logRecordOperationSource;

    private final String tenant;

    private final IFunctionService functionService;

    private BeanFactory beanFactory;

    public LogRecordInterceptor(LogRecordOperationSource logRecordOperationSource, String tenant,
                                      IFunctionService functionService) {
        this.logRecordOperationSource = logRecordOperationSource;
        this.tenant = tenant;
        this.functionService = functionService;
    }

    @Override
    public Object invoke(MethodInvocation invocation) throws Throwable {
        Method method = invocation.getMethod();
        // 记录日志
        return execute(invocation, invocation.getThis(), method, invocation.getArguments());
    }

    @Override
    public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
        this.beanFactory = beanFactory;
    }

    /**
     * 执行日志记录增强逻辑
     * @param invoker 执行方法
     * @param target 目标对象
     * @param method 方法对象
     * @param args 方法参数
     * @return 方法执行后的结果对象
     * @throws Throwable 如果方法在执行过程中发生异常，则会抛出异常
     */
    private Object execute(MethodInvocation invoker, Object target, Method method, Object[] args) throws Throwable {
        Class<?> targetClass = AopUtils.getTargetClass(target);
        // 非当前租户包路径，不作处理
        if (!targetClass.getName().startsWith(tenant)) {
            return invoker.proceed();
        }
        Object ret = null;
        MethodExecuteResult methodExecuteResult = new MethodExecuteResult(true, null, "");
        LogRecordContext.putEmptySpan();
        LogRecordValueParser logRecordValueParser = new LogRecordValueParser(functionService, logRecordOperationSource);
        LogRecordEvaluationContext evaluationContext = new LogRecordEvaluationContext(null, method, args,
                new DefaultParameterNameDiscoverer());
        Collection<LogRecordOps> operations = new ArrayList<>();
        Map<String, String> functionNameAndReturnMap = new HashMap<>(16);
        try {
            operations = logRecordOperationSource.computeLogRecordOperations(method);
            List<LogRecordOps> spElTemplateOps = getBeforeExecuteFunction(operations);
            //业务逻辑执行前的自定义函数解析
            functionNameAndReturnMap = processBeforeExecuteFunctionTemplate(logRecordValueParser, evaluationContext,
                    spElTemplateOps, targetClass, method, args);
        } catch (Exception e) {
            log.error("log record parse before function exception", e);
        }
        try {
            ret = invoker.proceed();
        } catch (Exception e) {
            methodExecuteResult = new MethodExecuteResult(false, e, e.getMessage());
        }
        try {
            recordExecute(logRecordValueParser, evaluationContext, operations, ret, method, targetClass,
                    methodExecuteResult.isSuccess(), methodExecuteResult.getErrorMsg(), functionNameAndReturnMap);
        } catch (Exception t) {
            //记录日志错误不要影响业务
            log.error("log record parse exception", t);
        } finally {
            LogRecordContext.clear();
        }
        if (methodExecuteResult.getThrowable() != null) {
            throw methodExecuteResult.getThrowable();
        }
        return ret;
    }

    /**
     * 获取所有需要在业务方法前执行的自定义方法
     * @param operations 所有的待执行语句
     * @return 所有需要在业务方法前执行的自定义方法
     */
    private List<LogRecordOps> getBeforeExecuteFunction(Collection<LogRecordOps> operations) {
        return operations.stream()
                .filter(o -> StatementType.CF == o.getStatementType())
                .filter(o -> functionService.beforeFunction(o.getFunctionName()))
                .collect(Collectors.toList());
    }

    /**
     * 需要在业务方法前执行的自定义方法的方法值map
     * @param logRecordValueParser 日志记录解析对象
     * @param evaluationContext 日志记录解析上下文对象
     * @param spElTemplateOps 自定义方法语句
     * @param targetClass 目标对象
     * @param method 方法对象
     * @param args 方法参数
     * @return 需要在业务方法前执行的自定义方法的方法值map
     */
    private Map<String, String> processBeforeExecuteFunctionTemplate(LogRecordValueParser logRecordValueParser,
                                                                     LogRecordEvaluationContext evaluationContext,
                                                                     List<LogRecordOps> spElTemplateOps,
                                                                     Class<?> targetClass, Method method, Object[] args) {
        Map<String, String> map = new HashMap<>(spElTemplateOps.size());
        for (LogRecordOps logRecordOps : spElTemplateOps) {
            map.put(logRecordOps.getFunctionName(), logRecordValueParser.parse(logRecordOps, method, targetClass, evaluationContext));
        }
        return map;
    }

    /**
     * 执行日志处理方法
     * @param logRecordValueParser 日志记录解析对象
     * @param evaluationContext 日志记录解析上下文对象
     * @param ret 业务方法执行后的结果对象
     * @param method 业务方法对象
     * @param targetClass 目标类型
     * @param success 是否执行成功
     * @param errorMsg 执行失败时的错误信息
     * @param functionNameAndReturnMap 前置自定义方法值map
     */
    private void recordExecute(LogRecordValueParser logRecordValueParser, LogRecordEvaluationContext evaluationContext,
                               Collection<LogRecordOps> operations, Object ret, Method method, Class<?> targetClass,
                               boolean success, String errorMsg, Map<String, String> functionNameAndReturnMap) {
        LogRecord logRecordAnnotation = method.getAnnotation(LogRecord.class);
        com.yeskery.boot.log.record.starter.domain.LogRecord logRecord = new com.yeskery.boot.log.record.starter.domain.LogRecord();
        evaluationContext.putLogRecordContext();
        evaluationContext.putExecuteResult(ret, errorMsg);
        String content, operator, bizNo, category = null, detail = null, condition = null;
        if (CollectionUtils.isEmpty(operations)) {
            content = success ? logRecordAnnotation.success() : logRecordAnnotation.fail();
            bizNo = logRecordAnnotation.bizNo();
        } else {
            content = success ? executeRecordContent(logRecordAnnotation.success(), logRecordValueParser,
                    evaluationContext, method, targetClass, functionNameAndReturnMap)
                    : executeRecordContent(logRecordAnnotation.fail(), logRecordValueParser, evaluationContext, method,
                    targetClass, functionNameAndReturnMap);
            bizNo = executeRecordContent(logRecordAnnotation.bizNo(), logRecordValueParser, evaluationContext, method,
                    targetClass, functionNameAndReturnMap);
            if (StringUtils.hasText(logRecordAnnotation.category())) {
                category = executeRecordContent(logRecordAnnotation.category(), logRecordValueParser, evaluationContext, method,
                        targetClass, functionNameAndReturnMap);
            }
            if (StringUtils.hasText(logRecordAnnotation.detail())) {
                detail = executeRecordContent(logRecordAnnotation.detail(), logRecordValueParser, evaluationContext, method,
                        targetClass, functionNameAndReturnMap);
            }
            if (StringUtils.hasText(logRecordAnnotation.condition())) {
                condition = executeRecordContent(logRecordAnnotation.condition(), logRecordValueParser, evaluationContext, method,
                        targetClass, functionNameAndReturnMap);
            }
        }

        logRecord.setContent(content);
        operator = StringUtils.hasText(logRecordAnnotation.operator())
                ? executeRecordContent(logRecordAnnotation.operator(), logRecordValueParser, evaluationContext, method,
                targetClass, functionNameAndReturnMap)
                : beanFactory.getBean(IOperatorGetService.class).getOperator().getName();
        logRecord.setOperator(operator);
        logRecord.setBizNo(bizNo);
        logRecord.setCategory(category);
        logRecord.setDetail(detail);
        logRecord.setCondition(condition);
        // 保存日志记录
        ILogRecordService logRecordService = beanFactory.getBean(ILogRecordService.class);
        logRecordService.record(logRecord);
    }

    /**
     * 获取模板语句解析后的值
     * @param template 模板语句
     * @param logRecordValueParser 日志记录解析对象
     * @param evaluationContext 日志记录解析上下文对象
     * @param method 业务方法对象
     * @param targetClass 目标类型
     * @param functionNameAndReturnMap 前置自定义方法值map
     * @return 模板语句解析后的值
     */
    private String executeRecordContent(String template, LogRecordValueParser logRecordValueParser,
                                        LogRecordEvaluationContext evaluationContext, Method method,
                                        Class<?> targetClass, Map<String, String> functionNameAndReturnMap) {
        Collection<LogRecordOps> logRecordOps = logRecordOperationSource.findLogRecordOps(template);
        for (LogRecordOps recordOps : logRecordOps) {
            if (recordOps.getStatementType() == StatementType.CF) {
                String value = functionNameAndReturnMap.get(recordOps.getFunctionName());
                if (!StringUtils.hasText(value)) {
                    value = logRecordValueParser.parse(recordOps, method, targetClass, evaluationContext);
                }
                template = template.replace(recordOps.getOriginalStatement(), value);
            } else {
                template = template.replace(recordOps.getOriginalStatement(),
                        logRecordValueParser.parse(recordOps, method, targetClass, evaluationContext));
            }
        }
        return CollectionUtils.isEmpty(logRecordOps) ? template : logRecordOperationSource.cleanIdentifier(template);
    }
}
